using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using data.contracts;
using generator.Properties;

namespace generator
{
    public static class InterfaceGenerator
    {
        public static string GenerateInterfaces(Board board) {
            Trace.TraceInformation("  Generate interfaces for board {0}", board.Name);

            return BoardSyntax.Pins(board.Wires)
                .Where(ItsNotTheBoard)
                .Where(pin => ItsNotAnExternalName(board, pin))
                .Aggregate("", (current, pin) => current + GenerateInterface(pin, board.Wires, board.Dependencies))
                    + GenerateInterfaceForBoard(board);
        }

        private static bool ItsNotTheBoard(Pin pin) {
            return pin.Board != "this";
        }

        private static bool ItsNotAnExternalName(Board board, Pin pin) {
            return
                !board.Externals.Contains(NamingRules.ToInterfaceName(pin)) &&
                    !NamingRules.IsTypeGiven(pin);
        }

        internal static string GenerateInterface(Pin pin, IEnumerable<Wire> wires, IEnumerable<Dependency> dependencies) {
            return Resources.InterfaceTemplate
                .Replace("$interfacename$", NamingRules.ToInterfaceName(pin))
                .Replace("$dependencies$", Dependencies(dependencies, NamingRules.ToInterfaceName(pin)))
                .Replace("$definition$", InterfaceDefinition(pin, wires, wire => wire.FromPin, wire => wire.ToPin));
        }

        private static string Dependencies(IEnumerable<Dependency> dependencies, string part) {
            var interfaces = dependencies
                .Where(x => NamingRules.ToInterfaceName(x.Dependent) == part);

            if (interfaces.IsEmpty()) {
                return "";
            }
            return " : " + string.Join(", ", interfaces.Select(x => string.Format("IDependsOn<{0}>", x.Type)));
        }

        private static string GenerateInterfaceForBoard(Board board) {
            if (board.Interface != null) {
                return "";
            }
            return Resources.InterfaceTemplate
                .Replace("$interfacename$", NamingRules.ToInterfaceName(board.Name))
                .Replace("$dependencies$", "")
                .Replace("$definition$", InterfaceDefinition(new Pin {Board = "this"}, board.Wires, wire => wire.ToPin, wire => wire.FromPin));
        }

        private static string InterfaceDefinition(Pin pin, IEnumerable<Wire> wires, Func<Wire, Pin> outputPinSelector, Func<Wire, Pin> inputPinSelector) {
            return
                GenerateOutputPinEvents(wires, pin, outputPinSelector) +
                    GenerateInputPinMethods(wires, pin, inputPinSelector);
        }

        private static string GenerateOutputPinEvents(IEnumerable<Wire> wires, Pin pin, Func<Wire, Pin> pinSelector) {
            var outputs = from wire in wires
                          where pinSelector(wire).Board == pin.Board
                          select new {Pin = BoardSyntax.Pin(pinSelector(wire), BoardSyntax.Direction.Output), wire.Type};
            return outputs
                .Distinct()
                .Aggregate("", (current, output) => current + GenerateEvent(output.Pin, output.Type));
        }

        private static string GenerateInputPinMethods(IEnumerable<Wire> wires, Pin pin, Func<Wire, Pin> pinSelector) {
            var inputs = from wire in wires
                         where pinSelector(wire).Board == pin.Board
                         select new {Pin = BoardSyntax.Pin(pinSelector(wire), BoardSyntax.Direction.Input), wire.Type};
            return inputs
                .Distinct()
                .Aggregate("", (current, input) => current + GenerateMethod(input.Pin, input.Type));
        }

        internal static string GenerateEvent(string name, string type) {
            return (string.IsNullOrEmpty(type) ? Resources.EventTemplate_noParam : Resources.EventTemplate)
                .Replace("$name$", name)
                .Replace("$type$", BoardSyntax.ParsedType(type));
        }

        internal static string GenerateMethod(string name, string parameterType) {
            return (string.IsNullOrEmpty(parameterType) ? Resources.MethodTemplate_noParam : Resources.MethodTemplate)
                .Replace("$name$", name)
                .Replace("$type$", BoardSyntax.ParsedType(parameterType));
        }
    }
}